﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Globalization;

namespace CalendarAPP
{
    public partial class MyCalendarControl : Control
    {
        #region 变量定义
        private DateTime choseDate = DateTime.Now;
        private DateTime currentDate = DateTime.Now;
        private string currentMDate = string.Empty;
        private Image backgroundImage = null;
        private bool pressed = false;
        private int ragWidth = 0;
        private int ragHeight = 0;
        private string wtitle = "日一二三四五六";
        private int week = 0;
        private int maxday = 0;
        private Font weekFont = new Font(new FontFamily("黑体"), 10, FontStyle.Bold);
        private Font lunisolarFont = new Font("Courier New", 11);
        private StringFormat sformat = new StringFormat();
        private Color todayColor = Color.Black;
        private ChineseLunisolarCalendar lunisolarCalendar = new ChineseLunisolarCalendar();
        #endregion

        #region 控件属性
        /// <summary>
        /// 当前日期
        /// </summary>
        public DateTime CurrentDate
        {
            get { return this.currentDate; }
            set { this.currentDate = value; }
        }

        /// <summary>
        /// 星期字体
        /// </summary>
        public Font WeekFont
        {
            get { return this.weekFont; }
            set { this.weekFont = value; }
        }

        /// <summary>
        /// 农历字体
        /// </summary>
        public Font LunisolarFont
        {
            get { return this.lunisolarFont; }
            set { this.lunisolarFont = value; }
        }

        /// <summary>
        /// 今日颜色
        /// </summary>
        public Color TodayColor
        {
            get { return this.todayColor; }
            set { this.todayColor = value; }
        }

        /// <summary>
        /// 农历日期
        /// </summary>
        public string LunisolarDate
        {
            get { return GetLunisolarYear(lunisolarCalendar.GetYear(currentDate)); }
        }

        /// <summary>
        /// 当前选中日期
        /// </summary>
        public string CurrentMDate
        {
            get { return currentMDate; }
        }

        /*
        public Image BackgroundImage
        {
            get { return this.backgroundImage; }
            set { this.backgroundImage = value; }
        }*/
        #endregion

        public MyCalendarControl()
        {
            InitializeComponent();
        }

        #region 控件事件
        protected override void OnPaint(PaintEventArgs pe)
        {
            initVal();
            //背景
            //pe.Graphics.FillRectangle();
            for (int i = 0; i < 7; i++)
            {
                pe.Graphics.FillRectangle(new SolidBrush(Color.Gray), i * ragWidth, 0, ragWidth, ragHeight / 2);
                pe.Graphics.DrawString(wtitle.Substring(i, 1), weekFont, new SolidBrush(Color.Black),
                    i * ragWidth + ragWidth / 2, ragHeight / 4, sformat);
            }

            int day = 1;
            bool isnowdate = currentDate.Year == DateTime.Now.Year ? (currentDate.Month == DateTime.Now.Month ? true : false) : false;
            DateTime currentNo1date = currentDate.AddDays(1 - currentDate.Day);
            for (int i = 1; i < 7; i++)
            {
                for (int j = 0; j < 7; j++)
                {
                    //pe.Graphics.FillRectangle(new SolidBrush(Color.LightYellow), j * ragWidth, i * ragHeight, ragWidth, ragHeight);
                    if (i == 1 && j < week) continue;
                    if (day > maxday) break;
                    //选择日期
                    //if (pressed)
                    //{
                        //Rectangle rect = new Rectangle(j * ragWidth, i * ragHeight, ragWidth, ragHeight);
                        //if (rect.Contains(pmouse))
                        //{
                        //    choseDate = currentNo1date.AddDays(day - 1);
                        //    pe.Graphics.DrawRectangle(new Pen(Color.Red, 1), rect);
                        //    currentMDate = choseDate.ToLongDateString();
                        //}
                    //}
                    if (choseDate.CompareTo(currentNo1date.AddDays(day)) == 0)
                    {
                        //pe.Graphics.DrawRectangle(new Pen(Color.Red, 1), j * ragWidth, i * ragHeight, ragWidth, ragHeight);
                        pe.Graphics.FillRectangle(new SolidBrush(todayColor), j * ragWidth, i * ragHeight, ragWidth, ragHeight);
                    }

                    //今日
                    if (isnowdate && day == currentDate.Day)
                    {
                        //pe.Graphics.FillRectangle(new SolidBrush(todayColor), j * ragWidth, i * ragHeight, ragWidth, ragHeight);
                        //pe.Graphics.FillEllipse(Brushes.Red, j * ragWidth, i * ragHeight, ragWidth, ragHeight);
                        pe.Graphics.DrawEllipse(new Pen(Color.Red, 3), j * ragWidth + 3, i * ragHeight + 5, ragWidth - 7, ragHeight - 10);
                    }

                    //公历
                    pe.Graphics.DrawString((day++).ToString(), this.Font, new SolidBrush(Color.Black),
                            j * ragWidth + ragWidth / 2, i * ragHeight + ragHeight / 3, sformat);
                    //农历
                    pe.Graphics.DrawString(getLunisolarDay(currentNo1date.AddDays(day - 2)), lunisolarFont, new SolidBrush(Color.Black),
                            j * ragWidth + ragWidth / 2, i * ragHeight + ragHeight * 3 / 4, sformat);
                    
                    
                }
            }
            base.OnPaint(pe);
        }

        private Point pmouse;
        private Rectangle prect;
        protected override void OnMouseDown(MouseEventArgs e)
        {
            //pressed = true; 
            pmouse = e.Location;

            Rectangle rect = new Rectangle();
            if (getChoseDate(pmouse, ref rect))
            {
                Region reg = new Region(prect);
                reg.Union(rect);
                this.Invalidate(reg);//.Refresh();
                prect = rect;
            }
            base.OnMouseDown(e);
        }

        protected override void OnMouseUp(MouseEventArgs e)
        {
            //pressed = false;
            base.OnMouseUp(e);
        }

        protected override void OnClick(EventArgs e)
        {
            base.OnClick(e);
        }
        #endregion

        #region 
        private void initVal()
        {
            Calendar calendar = CultureInfo.InvariantCulture.Calendar;
            //ChineseLunisolarCalendar lunisolarCalendar = new ChineseLunisolarCalendar();
            maxday = calendar.GetDaysInMonth(currentDate.Year, currentDate.Month);
            //int day = calendar.GetDayOfMonth(nowDate);
            //本月一号星期几
            week = (int)currentDate.AddDays(1 - currentDate.Day).DayOfWeek;
            ragWidth = this.Width / 7;
            ragHeight = this.Height / 7;
            sformat.Alignment = StringAlignment.Center;
            sformat.LineAlignment = StringAlignment.Center;
        }

        private bool getChoseDate(Point point, ref Rectangle rect)
        {
            int pX = point.X / ragWidth;
            int pY = point.Y / ragHeight;
            if (pY < 1) return false;
            int day = (pY - 1) * 7 + pX + 1 - week;
            if (day < 1 || day > maxday) return false;
            //if day>?? return false;
            choseDate = currentDate.AddDays(day - currentDate.Day + 1);
            rect = new Rectangle(pX * ragWidth, pY * ragHeight, ragWidth+1, ragHeight+1);
            return true;
        }

        #region 农历年

        /// <summary>
        /// 十天干
        /// </summary>
        private static string[] tiangan = { "甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸" };

        /// <summary>
        /// 十二地支
        /// </summary>
        private static string[] dizhi = { "子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥" };

        /// <summary>
        /// 十二生肖
        /// </summary>
        private static string[] shengxiao = { "鼠", "牛", "虎", "免", "龙", "蛇", "马", "羊", "猴", "鸡", "狗", "猪" };

        /// <summary>
        /// 返回农历天干地支年 
        /// </summary>
        /// <param name="year">农历年</param>
        /// <returns></returns>
        public static string GetLunisolarYear(int year)
        {
            if (year > 3)
            {
                int tgIndex = (year - 4) % 10;
                int dzIndex = (year - 4) % 12;

                return string.Concat(tiangan[tgIndex], dizhi[dzIndex], "[", shengxiao[dzIndex], "]");

            }

            throw new ArgumentOutOfRangeException("无效的年份!");
        }
        #endregion

        #region 农历月

        /// <summary>
        /// 农历月
        /// </summary>
        private static string[] months = { "正", "二", "三", "四", "五", "六", "七", "八", "九", "十", "十一", "十二(腊)" };


        /// <summary>
        /// 返回农历月
        /// </summary>
        /// <param name="month">月份</param>
        /// <returns></returns>
        public static string GetLunisolarMonth(int month)
        {
            if (month < 13 && month > 0)
            {
                return months[month - 1];
            }

            throw new ArgumentOutOfRangeException("无效的月份!");
        }


        #endregion

        #region 农历日

        /// <summary>
        /// 日
        /// </summary>
        private static string[] days1 = { "初", "十", "廿" };
        private static string[] days2 = { "一", "二", "三", "四", "五", "六", "七", "八", "九", "十" };

        /// <summary>
        /// 返回农历日
        /// </summary>
        /// <param name="date"></param>
        /// <returns></returns>
        private string getLunisolarDay(DateTime date)
        {
            SolarTerm[] solarterm = GetSolarTerm(date);
            foreach (SolarTerm solar in solarterm)
            {
                if (solar.day == date.Day) return solar.holiday;
            }
            
            int year = lunisolarCalendar.GetYear(date);
            int month = lunisolarCalendar.GetMonth(date);
            int day = lunisolarCalendar.GetDayOfMonth(date);
            if (day == 1)
            {
                string sformat = lunisolarCalendar.GetDaysInMonth(year, month) == 29 ? "{0}月小" : "{0}月大";
                int leapmoth = lunisolarCalendar.GetLeapMonth(year);
                if(leapmoth==0)
                    return string.Format(sformat, GetLunisolarMonth(month));

                sformat = string.Concat((month == leapmoth + 1 ? "闰" : ""),sformat);
                if (month > leapmoth)
                    return string.Format(sformat, GetLunisolarMonth(month - 1));
                else
                    return string.Format(sformat, GetLunisolarMonth(month));
            }
            else
            {
                return GetLunisolarDay(day);
            }
        }

        /// <summary>
        /// 转换农历日显示
        /// </summary>
        /// <param name="day"></param>
        /// <returns></returns>
        private static string GetLunisolarDay(int day)
        {
            if (day > 0 && day < 31)
            {
                if (day != 20 && day != 30)
                {
                    return string.Concat(days1[(day - 1) / 10], days2[(day - 1) % 10]);
                }
                else
                {
                    return string.Concat(days2[(day - 1) / 10], days1[1]);
                }
            }

            throw new ArgumentOutOfRangeException("无效的日!");
        }

        #endregion

        #region 节气取得
        private struct SolarTerm
        {
            public int day;
            public DateTime SolarTermDateTime;
            public string holiday;
        };
        /// <summary>
        /// 获取指定日期的节气。
        /// </summary>
        /// <param name="solarDateTime"></param>
        /// <returns></returns>
        private SolarTerm[] GetSolarTerm(DateTime solarDateTime)
        {
            return GetSolarTerm(solarDateTime.Year, solarDateTime.Month);
        }

        /// <summary>
        /// 获取指定日期的节气。
        /// </summary>
        /// <param name="year">要获取的年</param>
        /// <param name="month">要获取的月</param>
        /// <returns></returns>
        private SolarTerm[] GetSolarTerm(int year, int month)
        {
            string[] lunarHoliDayName ={ 
            "小寒", "大寒", "立春", "雨水","惊蛰", "春分", "清明", "谷雨","立夏", "小满", "芒种", "夏至", 
            "小暑", "大暑", "立秋", "处暑","白露", "秋分", "寒露", "霜降","立冬", "小雪", "大雪", "冬至"};
            SolarTerm[] solarTerm = new SolarTerm[2];

            for (int n = month * 2 - 1; n <= month * 2; n++)
            {
                SolarTerm st = new SolarTerm();
                double dd = Term(year, n, true);
                double sd1 = AntiDayDifference(2005, Math.Floor(dd));
                //double sm1 = Math.Floor(sd1 / 100);
                //int h = (int)Math.Floor((double)Tail(dd) * 24);
                //int min = (int)Math.Floor((double)(Tail(dd) * 24 - h) * 60);
                //int mmonth = (int)Math.Ceiling((double)n / 2);
                //int day = (int)sd1 % 100;
                //st.SolarTermDateTime = new DateTime(year, mmonth, day, h, min, 0);
                st.day = (int)sd1 % 100;
                st.holiday = lunarHoliDayName[n - 1];
                solarTerm[n - month * 2 + 1] = st;
            }
            return solarTerm;
        }

        //原始：S
        /// <summary>
        ///返回y年第n个节气（如小寒为1）的日差天数值（pd取值真假，分别表示平气和定气）
        /// </summary>
        private double Term(int y, int n, bool pd)
        {
            double juD = y * (365.2423112 - 6.4e-14 * (y - 100) * (y - 100) - 3.047e-8 * (y - 100)) + 15.218427 * n + 1721050.71301;//儒略日
            double tht = 3e-4 * y - 0.372781384 - 0.2617913325 * n;//角度
            double yrD = (1.945 * Math.Sin(tht) - 0.01206 * Math.Sin(2 * tht)) * (1.048994 - 2.583e-5 * y);//年差实均数
            double shuoD = -18e-4 * Math.Sin(2.313908653 * y - 0.439822951 - 3.0443 * n);//朔差实均数
            double vs = (pd) ? (juD + yrD + shuoD - EquivalentStandardDay(y, 1, 0) - 1721425) : (juD - EquivalentStandardDay(y, 1, 0) - 1721425);
            return vs;
        }

        /// <summary>
        /// 返回阳历y年日差天数为x时所对应的月日数（如y=2000，x=274时，返回1001(表示10月1日，即返回100*m+d)）
        /// </summary>
        private double AntiDayDifference(int y, double x)
        {
            int m = 1;
            for (int j = 1; j <= 12; j++)
            {
                int mL = DayDifference(y, j + 1, 1) - DayDifference(y, j, 1);
                if (x <= mL || j == 12)
                {
                    m = j;
                    break;
                }
                else
                    x -= mL;
            }
            return 100 * m + x;
        }

        //原始：tail
        /// <summary>
        /// 返回x的小数尾数，若x为负值，则是1-小数尾数
        /// </summary>
        private double Tail(double x)
        {
            return x - Math.Floor(x);
        }

        #region
        /// <summary>
        /// 判断y年m月(1,2,..,12,下同)d日是Gregorian历还是Julian历（opt=1,2,3分别表示标准日历,Gregorge历和Julian历）,是则返回1，是Julian历则返回0，若是Gregorge历所删去的那10天则返回-1
        /// </summary>
        private int IfGregorian(int y, int m, int d, int opt)
        {
            if (opt == 1)
            {
                if (y > 1582 || (y == 1582 && m > 10) || (y == 1582 && m == 10 && d > 14))
                    return (1);  //Gregorian
                else
                    if (y == 1582 && m == 10 && d >= 5 && d <= 14)
                        return (-1);  //空
                    else
                        return (0);  //Julian
            }

            if (opt == 2)
                return (1);  //Gregorian
            if (opt == 3)
                return (0);  //Julian
            return (-1);
        }

        /// <summary>
        /// 返回等效标准天数（y年m月d日相应历种的1年1月1日的等效(即对Gregorian历与Julian历是统一的)天数）
        /// </summary>
        private double EquivalentStandardDay(int y, int m, int d)
        {
            double v = (y - 1) * 365 + Math.Floor((double)((y - 1) / 4)) + DayDifference(y, m, d) - 2;  //Julian的等效标准天数
            if (y > 1582)
                v += -Math.Floor((double)((y - 1) / 100)) + Math.Floor((double)((y - 1) / 400)) + 2;  //Gregorian的等效标准天数
            return v;
        }

        /// <summary>
        /// 返回阳历y年m月d日的日差天数（在y年年内所走过的天数，如2000年3月1日为61）
        /// </summary>
        private int DayDifference(int y, int m, int d)
        {
            int ifG = IfGregorian(y, m, d, 1);
            int[] monL = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
            if (ifG == 1)
                if ((y % 100 != 0 && y % 4 == 0) || (y % 400 == 0))
                    monL[2] += 1;
                else
                    if (y % 4 == 0)
                        monL[2] += 1;
            int v = 0;
            for (int i = 0; i <= m - 1; i++)
            {
                v += monL[i];
            }
            v += d;
            if (y == 1582)
            {
                if (ifG == 1)
                    v -= 10;
                if (ifG == -1)
                    v = 0;  //infinity 
            }
            return v;
        }
        #endregion
        #endregion
        #endregion
    }
}
